/*
 * @(#) $Header$
 *
 * Copyright (C) 2007  Forklabs Daniel Léonard
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

package ca.forklabs.media.jai.opimage;

import java.awt.RenderingHints;
import java.awt.image.DataBuffer;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
import java.awt.image.renderable.ParameterBlock;
import java.util.Collection;
import javax.media.jai.BorderExtender;
import javax.media.jai.CollectionImage;
import javax.media.jai.CollectionImageFactory;
import javax.media.jai.CollectionOp;
import javax.media.jai.JAI;
import javax.media.jai.operator.DFTDataNature;
import javax.media.jai.operator.DFTScalingType;
import com.sun.media.jai.util.MathJAI;
import ca.forklabs.media.jai.ParameterBlockUtil;
import ca.forklabs.media.jai.SimpleCollectionImage;
import ca.forklabs.media.jai.operator.DFT3DDescriptor;

/**
 * Class {@code AbstractDFT3DCIF} provides common code for operator
 * <em>dft3d</em> and <em>idft3d</em>.
 *
 * @author   <a href="mailto:forklabs at gmail.com?subject=ca.forklabs.media.jai.opimage.AbstractDFT3DCIF">Daniel Léonard</a>
 * @version $Revision$
 */
public abstract class AbstractDFT3DCIF implements CollectionImageFactory {

//---------------------------
// Constructor
//---------------------------

   /**
    * Constructor.
    */
   protected AbstractDFT3DCIF() {
   // nothing
      }


//---------------------------
// Abstract methods
//---------------------------

   protected abstract DFT3DOpImage.Transform getTransform();


//---------------------------
// Instance methods
//---------------------------

   /**
    * Gets the name of the operation parameter within the parameter block.
    * @param   pb   the parameter block.
    * @return   the name of the operation.
    */
   protected DFTScalingType getScaling(ParameterBlock pb) {
      int index = DFT3DDescriptor.SCALING_PARAMETER_INDEX;
      DFTScalingType strategy = (DFTScalingType) pb.getObjectParameter(index);
      return strategy;
      }

   /**
    * Gets the name of the operation parameter within the parameter block.
    * @param   pb   the parameter block.
    * @return   the name of the operation.
    */
   protected DFTDataNature getNature(ParameterBlock pb) {
      int index = DFT3DDescriptor.NATURE_PARAMETER_INDEX;
      DFTDataNature nature = (DFTDataNature) pb.getObjectParameter(index);
      return nature;
      }

   /**
    * Gets the next positive power of 2 greater than or equal to the parameter
    * passed. It is to note that 1 is a power of 2, in fact it is 2<sup>0</sup>.
    * @param   value   the value.
    * @return   the next positive power of 2.
    */
   protected int nextPositivePowerOf2(int value) {
      int power_of_2 = (1 == value) ? 1 : MathJAI.nextPositivePowerOf2(value);
      return power_of_2;
      }

   /**
    * Prepares the source images so that they are fit for the Fourier transform.
    * @param   sources   the source images.
    * @param   hints   the rendering hints.
    * @return   the collection image containing the results.
    */
   @SuppressWarnings({ "nls", "boxing" })
   public CollectionImage prepareSources(Collection<RenderedImage> sources, RenderingHints hints) {
      int depth = sources.size();

      int max_width = -1;
      int max_height = -1;
      int max_depth = this.nextPositivePowerOf2(depth);
      int max_bands = -1;
      int max_data_type = DataBuffer.TYPE_FLOAT;

      for (RenderedImage source : sources) {
         int width = source.getWidth();
         int height = source.getHeight();

         SampleModel sample_model = source.getSampleModel();
         int bands = sample_model.getNumBands();

         int data_type = sample_model.getDataType();

         max_width = Math.max(max_width, width);
         max_height = Math.max(max_height, height);
         max_bands = Math.max(max_bands, bands);

      // DataBuffer.TYPE_DOUBLE > DataBuffer.TYPE_FLOAT
         switch (data_type) {
            case DataBuffer.TYPE_BYTE:
            case DataBuffer.TYPE_SHORT:
            case DataBuffer.TYPE_USHORT:
            case DataBuffer.TYPE_INT:
            case DataBuffer.TYPE_FLOAT:
               max_data_type = Math.max(max_data_type, DataBuffer.TYPE_FLOAT);
               break;
            case DataBuffer.TYPE_DOUBLE:
               max_data_type = Math.max(max_data_type, DataBuffer.TYPE_DOUBLE);
               break;
            default:
               String message = this.getBadDataTypeErrorMessage(data_type);
               throw new IllegalStateException(message);
            }
         }
      max_width = this.nextPositivePowerOf2(max_width);
      max_height = this.nextPositivePowerOf2(max_height);

      CollectionImage sinks = new SimpleCollectionImage();

   // it is either float or double
      Number[] values = (DataBuffer.TYPE_FLOAT == max_data_type) ? new Float[] { 0.0f, } : new Double[] { 0.0, };
      ParameterBlock black_pb = ParameterBlockUtil.createConstantParameterBlock(max_width, max_height, values);
      RenderedImage black = JAI.create("constant", black_pb, hints);

      for (Object source : sources) {
         RenderedImage image = (RenderedImage) source;
         int left = 0;
         int top = 0;
         int right = max_width - image.getWidth();
         int bottom = max_height - image.getHeight();
         BorderExtender extender = BorderExtender.createInstance(BorderExtender.BORDER_ZERO);
         ParameterBlock border_pb = ParameterBlockUtil.createBorderParameterBlock(image, left, right, top, bottom, extender);
         RenderedImage sink = JAI.create("border", border_pb, hints);

         SampleModel sample_model = image.getSampleModel();
         int bands = sample_model.getNumBands();
         for (int i = bands; i < max_bands; i++) {
            ParameterBlock merge_pb = ParameterBlockUtil.createBandMergeParameterBlock(sink, black);
            sink = JAI.create("bandmerge", merge_pb, hints);
            }

         int data_type = sample_model.getDataType();
         if (data_type != max_data_type) {
            ParameterBlock format_pb = ParameterBlockUtil.createFormatParameterBlock(sink, max_data_type);
            sink = JAI.create("format", format_pb, hints);
            }

         sinks.add(sink);
         }

      if (max_depth > depth) {
         RenderedImage padding = black;
         for (int i = 1; i < max_bands; i++) {
            ParameterBlock merge_pb = ParameterBlockUtil.createBandMergeParameterBlock(padding, black);
            padding = JAI.create("bandmerge", merge_pb, hints);
            }

         for (int i = depth; i < max_depth; i++) {
            sinks.add(padding);
            }
         }

      return sinks;
      }

   /**
    * Gets the error message saying that the data type is unknown.
    * @param   type   the data type.
    * @return   the message.
    */
   @SuppressWarnings("boxing")
   protected String getBadDataTypeErrorMessage(int type) {
      String key = Resources.UNKNOWN_DATA_TYPE;
      String message = Resources.getLocalizedString(key, type);
      return message;
      }


//---------------------------
// Implemented methods from javax.media.jai.CollectionImageFactory
//---------------------------

   /**
    * Creates a {@code CollectionImage} that represents the result of applying
    * the requested operation once to each image in the collection.
    * @param   pb   the parameter block.
    * @param   hints   the rendering hints.
    * @return   the collection image containing the results.
    */
   @SuppressWarnings("unchecked")
   public CollectionImage create(ParameterBlock pb, RenderingHints hints) {
      CollectionImage sources = (CollectionImage) pb.getSource(0);

      CollectionImage sinks = this.prepareSources(sources, hints);
      DFT3DOpImage.Transform transform = this.getTransform();
      DFTScalingType scaling = this.getScaling(pb);
      DFTDataNature nature = this.getNature(pb);

      CollectionImage collection_image = new DFT3DOpImage(sinks, transform, scaling, nature);
      return collection_image;
      }

   /**
    * It is impratical to perform the update.
    * @param old_pb   ignored
    * @param old_hints   ignored
    * @param new_pb   ignored
    * @param new_hints   ignored
    * @param old_image   ignored
    * @param op   ignored
    * @return always {@code null}.
    */
   public CollectionImage update(ParameterBlock old_pb, RenderingHints old_hints,
                                 ParameterBlock new_pb, RenderingHints new_hints,
                                 CollectionImage old_image,
                                 CollectionOp op) {
   // it is impracticable to perform the update
      CollectionImage new_image = null;
      return new_image;
      }

   }
